#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/select.h>
#include <signal.h>
#define PATH "/dev/my_key"
#define OPT_STRING    ":a:"   // 选项字符串
static void print_usage()
{
    printf("Usage:    ./test -a <b/nb/poll/select/signal>\n"
           "-a:       running mode\n"
           "b:        block\n"
           "nb:       non-block\n" 
           "poll:     poll \n"
           "select:   select\n"
           "signal:   asynchronous notification\n");
}

extern char* optarg;  // 指向参数值
extern int optind;    // 记录getopt函数处理argv[]数组的位置，一般不需要设置
extern int opterr;    // 保存出错信息，非0 getopt会向stderr打印出错信息，如为0时则不打印
/* 遇到无法识别的选项，getopt返回？号，并将？存储到optopt中，如果将选项字符串第一个字符设置
   为：号，那么getopt函数在用户未提供值的情况下返回：号而不是？号 */
extern int optopt; 
static int parse_option(int argc, char* argv[], int* mode)
{
    int opt;
    if (3 != argc) {
        print_usage();
        return -1;       
    }
    while (-1 != (opt = getopt(argc, argv, OPT_STRING))) {
        switch (opt) {
        case 'a':
            if ('b' == *optarg) mode = 0;
            else if (0 == strcmp(optarg, "nb")) *mode = 1;
            else if (0 == strcmp(optarg, "poll")) *mode = 2; 
            else if (0 == strcmp(optarg, "select")) *mode = 3;
            else if (0 == strcmp(optarg, "signal")) *mode = 4;
            else return -1; 
            break;
        case ':':
            print_usage();
            return -1;
        case '?':
            print_usage();
            return -1;

        default:
            print_usage();
            return -1;
        }
    }
    return 0;
}
static int fd;
static unsigned int push_cnt = 0, release_cnt = 0;
static void read_key(int fd)
{
    int len;
    char val = 0xFF;         
    len = read(fd, &val, sizeof(val));
    if (sizeof(val) == len) {
        if (1 == val) {
            push_cnt++;
            printf("push the button %d\n", push_cnt);
        } 
        if (0 == val) {
            release_cnt++;
            printf("release the button %d\n", release_cnt);
        }
    }
}
// 信号处理函数
static void my_signal_fun(int signum)
{
    read_key(fd);
}

int main(int argc, char* argv[])
{
    int len, ret;
    int flags = 0, mode = 0;
    char val = 0xFF;
    struct pollfd fds = {0};
    struct timeval timeout = {0};
    fd_set read_fds;
    ret = parse_option(argc, argv, &mode);
    if (0 != ret) return -1;

    switch (mode) {
    case 0:
        fd = open(PATH, O_RDONLY); // 阻塞打开
        if (fd < 0){
            printf("my_key open error\n");
            return -1;
        }
        while (1) read_key(fd);
        break;
    case 1:
        fd = open(PATH, O_RDONLY | O_NONBLOCK);  // 非阻塞打开
        if (fd < 0){
            printf("my_key open error\n");
            return -1;
        }
        while (1) read_key(fd);
        break;
    case 2:
        fd = open(PATH, O_RDONLY); 
        if (fd < 0){
            printf("my_key open error\n");
            return -1;
        }
        fds.fd = fd;
        fds.events = (POLLIN | POLLRDNORM);
        while (1) {
            //fds.revents = 0;
            ret = poll(&fds, 1, 4000);  // 超时时间设置为4秒，
            if (ret < 0) {
                printf("poll error\n");
                close(fd);
                return -1;
            } 
            else if ((fds.revents & POLLIN) || (fds.revents & POLLRDNORM))
                read_key(fd);
            else printf("poll timeout\n");
        }
        break;
    case 3:
        FD_ZERO(&read_fds);
        fd = open(PATH, O_RDONLY);
        if (fd < 0){
            printf("my_key open error\n");
            return -1;
        }
        while (1) {
            FD_SET(fd, &read_fds);      
            timeout.tv_usec = 0;           
            timeout.tv_sec = 4;  // 超时时间设置为4秒
            ret = select(fd + 1, &read_fds, NULL, NULL, &timeout);
            if (ret < 0) {
                printf("select error\n");
                close(fd);
                return -1;
            } 
            else if (FD_ISSET(fd, &read_fds)) 
                read_key(fd);
            else printf("select timeout\n");
        }
        break;
    case 4:
        fd = open(PATH, O_RDONLY); 
        if (fd < 0){
            printf("my_key open error\n");
            return -1;
        }
        fcntl(fd, F_SETOWN, getpid());
        flags = fcntl(fd, F_GETFL);
        fcntl(fd, F_SETFL, flags | FASYNC);
        signal(SIGIO, my_signal_fun); 
        while (1) sleep(4);
        break;
    default:
        break;
    }
    close(fd);
    return 0;
}
